package cn.anxin.a9.avplayer;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Handler;
import android.os.Looper;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;

public class AVCamera implements AVGLListener {

    private AVEvent.Listener<Event> mEventListener = null;
    private Handler mHandler = null;

    private AVGLMonitor mMonitor = null;
    private AudioTrack mAudioTrack = null;
    private boolean isG711Running = false;
    private boolean isH264Running = false;
    private boolean isAudioMute = false;
    private byte[] yuvBuffer = null;
    private boolean isDecoderBuffer = false;
    private int yuvWidth = 0;
    private int yuvHeight = 0;
    static {
        System.loadLibrary("avjni");
    }

    //used by jni begin
    private long handler;
    //used by jni end

    private static AVCamera instance;

    private AVCamera(){
        this.init();
        this.h264Init();
        this.g711Init();
    }

    public static synchronized AVCamera getInstance(){
        if(instance == null){
            instance = new AVCamera();
        }
        return instance;
    }

    public void setMonitor(AVGLMonitor monitor){
        this.mMonitor = monitor;
        mMonitor.setAVGLListener(this);
    }

    public native void init();

    public native void h264Init();

    public native void h264Uninit();

    public native void h264Start();

    public native void h264Stop();

    public native void h264Decoder();

    public native void g711Init();

    public native void g711Uninit();

    public native void g711Start();

    public native void g711Stop();

    public native void g711Decoder();

    private int onH264ProgressCallBack(byte[] yuv_data, int yuv_size,int yuv_width,int yuv_height) {
        if(this.mMonitor != null && yuv_data != null) {
            this.mMonitor.setYuvFrameData(yuv_data, yuv_width, yuv_height);
            this.yuvBuffer = yuv_data;
            this.yuvHeight = yuv_height;
            this.yuvWidth = yuv_width;
            if(!isDecoderBuffer){
                isDecoderBuffer = true;
                dispatchEvent(Event.Vout);
            }
        }
        return 1;
    }

    //c层回调上来的方法
    private int onG711ProgressCallBack(byte[] g711_data, int g711_size) {
        if(this.mAudioTrack != null && g711_data != null&&!isAudioMute) {
            this.mAudioTrack.write(g711_data, 0, g711_size);
        }
        return 1;
    }

    private int onH264EndCallBack() {
        //this.h264Uninit();
        this.isH264Running = false;
        return 1;
    }

    private int onG711EndCallBack() {
        //this.g711Uninit();
        this.mAudioTrack.stop();
        this.mAudioTrack.release();
        this.mAudioTrack = null;
        this.isG711Running = false;
        return 1;
    }

    private boolean startH264(){
        if(this.isH264Running){
            return false;
        }
        this.isH264Running = true;
        this.h264Start();
        this.h264Decoder();
        return true;
    }

    private boolean startG711(){
        if(this.isG711Running){
            return false;
        }
        this.isG711Running = true;
        int sampleRateInHz = 8000;
        int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        int mMinBufSize = 0;
        mMinBufSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        if ((mMinBufSize == -2) || (mMinBufSize == -1)) {
            return false;
        }
        try
        {
            this.mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, mMinBufSize,  AudioTrack.MODE_STREAM);
        }
        catch (IllegalArgumentException iae)
        {
            iae.printStackTrace();
            return false;
        }
        this.mAudioTrack.setStereoVolume(1.0F, 1.0F);
        this.mAudioTrack.play();
        this.g711Start();
        this.g711Decoder();
        return true;
    }

    public static void toRGB565(byte[] yuvs, int width, int height, int[] rgbs) {
        // the end of the luminance data
        final int lumEnd = width * height;
        // points to the next luminance value pair
        int lumPtr = 0;
        // points to the next chromiance value pair
        int chrPtr = lumEnd;
        int chr2Ptr = lumEnd + (lumEnd >> 2);
        // points to the next byte output pair of RGB565 value
        int outPtr = 0;
        // the end of the current luminance scanline
        int lineEnd = width;

        while (true) {

            // skip back to the start of the chromiance values when necessary
            if (lumPtr == lineEnd) {
                if (lumPtr == lumEnd)
                    break; // we've reached the end
                // division here is a bit expensive, but's only done once per
                // scanline
                chrPtr = lumEnd + ((lumPtr >> 2) / width) * width;
                chr2Ptr = chrPtr + (lumEnd >> 2);
                lineEnd += width;
            }

            // read the luminance and chromiance values
            final int Y1 = yuvs[lumPtr++] & 0xff;
            final int Y2 = yuvs[lumPtr++] & 0xff;
            final int U = (yuvs[chrPtr++] & 0xff);
            final int V = (yuvs[chr2Ptr++] & 0xff);
            int C1 = Y1 - 16;
            int C2 = Y2 - 16;
            int D = U - 128;
            int E = V - 128;
            int R, G, B;

            // generate first RGB components
            B = ((298 * C1 + 516 * D + 128) >> 8);
            if (B < 0)
                B = 0;
            else if (B > 255)
                B = 255;
            G = ((298 * C1 - 100 * D - 208 * E + 128) >> 8);
            if (G < 0)
                G = 0;
            else if (G > 255)
                G = 255;
            R = ((298 * C1 + 409 * E + 128) >> 8);
            if (R < 0)
                R = 0;
            else if (R > 255)
                R = 255;
            // NOTE: this assume little-endian encoding
            rgbs[outPtr++] = Color.rgb(R, G, B);

            // generate second RGB components
            B = ((298 * C2 + 516 * D + 128) >> 8);
            if (B < 0)
                B = 0;
            else if (B > 255)
                B = 255;
            G = ((298 * C2 - 100 * D - 208 * E + 128) >> 8);
            if (G < 0)
                G = 0;
            else if (G > 255)
                G = 255;
            R = ((298 * C2 + 409 * E + 128) >> 8);
            if (R < 0)
                R = 0;
            else if (R > 255)
                R = 255;
            // NOTE: this assume little-endian encoding
            rgbs[outPtr++] = Color.rgb(R, G, B);
        }
    }

    public void play(){
        startG711();
        startH264();
        this.isDecoderBuffer = false;
        dispatchEvent(Event.Playing);
    }

    public void stop(){
        h264Stop();
        g711Stop();
        this.yuvBuffer = null;
        this.isDecoderBuffer = false;
        dispatchEvent(Event.Stopped);
    }

    public void mute(boolean flag){
        this.isAudioMute = flag;
    }

    public Bitmap getSnapshot(){
        if(isDecoderBuffer){
            int[] rgb = new int[yuvWidth*yuvHeight];
            toRGB565(yuvBuffer,yuvWidth,yuvHeight,rgb);
            Bitmap bitmap = Bitmap.createBitmap(rgb, yuvWidth,
                    yuvHeight, Bitmap.Config.RGB_565);
            return bitmap;
        }
        return null;
    }

    public void setScalable(boolean scalable){
        this.mMonitor.setScalable(scalable);
    }

    public void setMaxScaleLevel(float level){
        this.mMonitor.setMaxScaleLevel(level);
    }

    public synchronized void setEventListener(EventListener listener) {
        this.setEventListener(listener,null);
    }

    protected synchronized void setEventListener(AVEvent.Listener<AVCamera.Event> listener, Handler handler) {
        if (mHandler != null)
            mHandler.removeCallbacksAndMessages(null);
        mEventListener = listener;
        if (mEventListener == null)
            mHandler = null;
        else if (mHandler == null)
            mHandler = handler != null ? handler : new Handler(Looper.getMainLooper());
    }

    private void dispatchEvent(int eventType) {
        dispatchEvent(eventType, null);
    }

    private synchronized void dispatchEvent(int eventType, Map params) {
        final Event event = new Event(eventType, params);

        class EventRunnable implements Runnable {
            private final AVEvent.Listener<Event> listener;
            private final Event event;

            private EventRunnable(AVEvent.Listener<Event> listener, Event event) {
                this.listener = listener;
                this.event = event;
            }
            @Override
            public void run() {
                listener.onEvent(event);
            }
        }

        if (event != null && mEventListener != null && mHandler != null)
            mHandler.post(new EventRunnable(mEventListener, event));
    }

    @Override
    public void onScale(final float scale) {
        Map params = new HashMap(){{
            put("scale", scale);
        }};
        dispatchEvent(Event.Scale, params);
    }

    public static class Event extends AVEvent {

        public static final int Playing = 0x100;
//        public static final int Paused = 0x101;
        public static final int Stopped = 0x102;
        public static final int Vout = 0x103;
        public static final int Scale = 0x104;

        protected Event(int type) {
            super(type);
        }

        protected Event(int type, Map params) {
            super(type, params);
        }
    }

    public interface EventListener extends AVEvent.Listener<AVCamera.Event> {}
}
